#include <QXmlSimpleReader>
#include <QBuffer>
#include <QFile>
#include <memory>
#include "XMLHandler.h"
#include "Tags.h"
#include "Draft.h"

using namespace tag;

QString Draft::lastError = "Draft ok.";
    
DraftPtr Draft::FromFile(QFile* file)
{  
    QString errMsg(QString("Draft::FromFile: file \"%1\" doesn't exist or can't be opened").arg(file->fileName()));
    return file->exists() && file->open(QFile::ReadOnly | QFile::Text) ? GetDraft(file) : (SetError(errMsg), DraftPtr());
}

DraftPtr Draft::FromString(const QString& string)
{
    QString errMsg("Draft::FromString: string is empty");
    return !string.isEmpty() ? GetDraft(&QBuffer(&string.toUtf8())) : (SetError(errMsg), DraftPtr());
}

DraftPtr Draft::GetDraft(QIODevice* io)
{
    DraftPtr draft(new Draft);

    XMLHandler handler(draft);
    QXmlSimpleReader reader;
    reader.setContentHandler(&handler);
    reader.setErrorHandler(&handler);

    return reader.parse(QXmlInputSource(io)) ? draft : (SetError(handler.GetError()), DraftPtr());
}

Draft::Draft() 
: transferNum(0)
{
    RegisterHandlers();
    DrawMinimal();
}

void Draft::RegisterHandlers()
{
    RegisterCommon();
    RegisterChannel();
}

void Draft::RegisterCommon()
{
    _tagHandlers.insert(tag::QProcedure, &Draft::AddProcedure);
    _tagHandlers.insert(tag::QTransfer, &Draft::AddTransfer);
    _tagHandlers.insert(tag::QInitial, &Draft::SetInitial);
    _tagHandlers.insert(tag::QAction, &Draft::AddAction);
    _tagHandlers.insert(tag::QGlobal, &Draft::AddGlobal);
    _tagHandlers.insert(tag::QState, &Draft::AddState);
    _tagHandlers.insert(tag::QChar, &Draft::AddChar);
}

void Draft::RegisterChannel()
{
    _tagHandlers.insert(tag::QChannel, &Draft::AddChannel);
    _tagHandlers.insert(tag::QProperty, &Draft::AddProperty);
}

void Draft::DrawMinimal()
{
    state2procedures[tag::AExit];
}

bool Draft::AddChannel(const StringMap& tuple)
{
    channels[channel = tuple[AName]][AType] = tuple[AType];
    return true;
}

bool Draft::AddProperty(const StringMap& tuple)
{
    bool ok = !channel.isEmpty();

    if (ok) 
        channels[channel][tuple[AName]] = tuple[AValue];

    return ok;
}

bool Draft::AddGlobal(const StringMap& tuple)
{
    bool uniq = !globals.contains(tuple[AName]);
    
    if (uniq)
        globals[tuple[AName]] = tuple[AValue];
    
    return uniq;
}

bool Draft::AddState(const StringMap& tuple)
{
    currentState = tuple[AName];
    transferNum  = 0;
    return true;
}

bool Draft::SetInitial(const StringMap& tuple)
{
    initialState = tuple[AName];   
    return true;
}

bool Draft::AddTransfer(const StringMap& tuple)
{
    transferUID = currentState + QString("%1").arg(++transferNum);
    state2transfers[currentState].append(transferUID);
    transfer2state[transferUID] = tuple[tag::ADest];

    return true;
}

bool Draft::AddChar(const StringMap& tuple)
{
    transfer2char[transferUID].append(tuple[AValue]);
    return true;
}  

bool Draft::AddProcedure(const StringMap& tuple)
{
    state2procedures[currentState].append(tuple);
    return true;
}

bool Draft::AddAction(const StringMap& tuple)
{
    transfer2actions[transferUID].append(tuple);
    return true;
}

bool Draft::isValid()
{
    CheckTransfersDest();
    CheckInitialExist();
    
    return validationErrorList.isEmpty();
}

void Draft::CheckInitialExist()
{
    if (transfer2state.values().indexOf(initialState) == -1)    
        validationErrorList.append(QString("Initial state error: can't find state \"%1\"").arg(initialState));
}

void Draft::CheckTransfersDest()
{
    Q_FOREACH(const QString& state, transfer2state.values())
        if (!state2transfers.contains(state) && !state2procedures.contains(state))
            validationErrorList.append(QString("Dest \"%1\" is incorrect").arg(state));
}

QString Draft::GetValidationErrors(const QString& separator /* = "\n" */)
{
    return validationErrorList.join(separator);
}

DraftPtr Draft::Default()
{
    // TODO: default draft receive-save
    //return Draft::FromString();
    return DraftPtr(new Draft); 
}

bool Draft::OnTag(const QString& tag, const StringMap& attrPairs, bool passUnknown)
{
    return _tagHandlers.contains(tag) ? (this->*_tagHandlers[tag])(attrPairs) : passUnknown;    
}

const StringMapMap& Draft::Channels() const
{
    return channels;
}

void Draft::SetError(const QString& error)
{
    lastError = error;
}

QString Draft::LastError()
{
    return lastError;
}
